In [ ]:
#include <thread>
#include <iostream>
#include <vector>
#include <future>
#include <chrono>

In this post, we are going to talk about futures, more precisely std::future. What is a future ? It's a very nice and simple mechanism to work with asynchronous tasks. It also has the advantage of decoupling you from the threads themselves, you can do multithreading without using std::thread. The future itself is a structure pointing to a result that will be computed in the future. How to create a future ? The simplest way is to use std::async that will create an asynchronous task and return a std::future.

In [ ]:
auto future = std::async(std::launch::async, [](){
    std::cout << "I'm a thread" << std::endl;
});

future.get();

Nothing really special here. std::async will execute the task that we give it (here a lambda) and return a std::future. Once you use the get() function on a future, it will wait until the result is available and return this result to you once it is. The get() function is then blocking. Since the lambda, is a void lambda, the returned future is of type std::future and get() returns void as well. It is very important to know that you cannot call get several times on the same future. Once the result is consumed, you cannot consume it again! If you want to use the result several times, you need to store it yourself after you called get().

Let's see with something that returns a value and actually takes some time before returning it:

In [ ]:
auto future = std::async(std::launch::async, [](){
    std::this_thread::sleep_for(std::chrono::seconds(5));
    return 42;
});

// Do something else ?

std::cout << future.get() << std::endl;

This time, the future will be of the time std::future and thus get() will also return an int. std::async will again launch a task in an asynchronous way and future.get() will wait for the answer. What is interesting, is that you can do something else before the call to future.

But get() is not the only interesting function in std::future. You also have wait() which is almost the same as get() but does not consume the result. For instance, you can wait for several futures and then consume their result together. But, more interesting are the wait_for(duration) and wait_until(timepoint) functions. The first one wait for the result at most the given time and then returns and the second one wait for the result at most until the given time point. I think that wait_for is more useful in practices, so let's discuss it further. Finally, an interesting function is bool valid(). When you use get() on the future, it will consume the result, making valid() returns :code:`false. So, if you intend to check multiple times for a future, you should use valid() first.

One possible scenario would be if you have several asynchronous tasks, which is a common scenario. You can imagine that you want to process the results as fast as possible, so you want to ask the futures for their result several times. If no result is available, maybe you want to do something else. Here is a possible implementation:

In [ ]:
auto f1 = std::async(std::launch::async, [](){
    std::this_thread::sleep_for(std::chrono::seconds(9));
    return 42;
});

auto f2 = std::async(std::launch::async, [](){
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 13;
});

auto f3 = std::async(std::launch::async, [](){
    std::this_thread::sleep_for(std::chrono::seconds(6));
    return 666;
});

auto timeout = std::chrono::milliseconds(10);

while(f1.valid() || f2.valid() || f3.valid()){
    if(f1.valid() && f1.wait_for(timeout) == std::future_status::ready){
        std::cout << "Task1 is done! " << f1.get() << std::endl;
    }

    if(f2.valid() && f2.wait_for(timeout) == std::future_status::ready){
        std::cout << "Task2 is done! " << f2.get() << std::endl;
    }

    if(f3.valid() && f3.wait_for(timeout) == std::future_status::ready){
        std::cout << "Task3 is done! " << f3.get() << std::endl;
    }

    std::cout << "I'm doing my own work!" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "I'm done with my own work!" << std::endl;
}

std::cout << "Everything is done, let's go back to the tutorial" << std::endl;

The three tasks are started asynchronously with std::async and the resulting std::future are stored. Then, as long as one of the tasks is not complete, we query each three task and try to process its result. If no result is available, we simply do something else. This example is important to understand, it covers pretty much every concept of the futures.

One interesting thing that remains is that you can pass parameters to your task via std::async. Indeed, all the extra parameters that you pass to std::async will be passed to the task itself. Here is an example of spawning tasks in a loop with different parameters:

In [ ]:
std::vector<std::future<size_t>> futures;
    
for (size_t i = 0; i < 10; ++i) {
    futures.emplace_back(std::async(std::launch::async, [](size_t param){
        std::this_thread::sleep_for(std::chrono::seconds(param));
        return param;
    }, i));
}
    
std::cout << "Start querying" << std::endl;
    
for (auto &future : futures) {
    std::cout << future.get() << std::endl;
}

Pretty practical :) All The created std::future are stored in a std::vector and then are all queried for their result. Overall, I think std::future and std::async are great tool that can simplify your asynchronous code a lot. They allow you to make pretty advanced stuff while keeping the complexity of the code to a minimum.